package YAPserver.gameObjects;

import YAPenvironment.YAPconstant;
/**
 * This class is one of two building blocks of all gameObjects. Each gameObject is built of lines and points.
 * This class creates a geometric line with a start point, end point, an angle and a length. Though angle and length are worked out from the starting and end points.
 * @author Daniel
 */
public class Line {
	private double m_angle; //ranging from 0 to 2*PI, corresponds to direction
	private double m_length;
	private Point m_startPoint;
	private Point m_endPoint;
	private static String tab = YAPconstant.C_SEPARATOR; 
	
	/**
	 * Constructs a new line.
	 * @param startPoint an end point of the line
	 * @param endPoint another end point of the line
	 */
	public Line(Point startPoint, Point endPoint){
		m_startPoint = startPoint;
		m_endPoint = endPoint;
		calcLength(startPoint,endPoint);
		calcAngle(startPoint,endPoint,m_length);
	}
	
	/**
	 * Constructs a new line.
	 * @param length the length the line should have
	 * @param angle the direction of the line
	 * @param startPoint an end point of the line
	 */
	public Line(double length, double angle, Point startPoint){
		m_length = length;
		m_angle = angle;
		m_startPoint = startPoint;
		m_endPoint = new Point(0, 0);
		calcEndPoint(length, angle, startPoint);
	}
	
	/**
	 * Constructs an exact copy of the line.
	 * @param line line to be copied
	 */
	public Line(Line line) {
		m_startPoint = new Point(line.getStartPoint());
		m_endPoint = new Point(line.getEndPoint());
		m_angle = line.getAngle();
		m_length = line.getLength();
	}

	//this method needs testing or reviewing
	//if there is an intersection, it should return true
	/**
	 * Tests if there is an intersection between this line and another line.
	 * @param line line to test intersection with
	 * @return <code>true</code> if there is an intersection and otherwise <code>false</code>
	 */
	public boolean hasIntersection(Line line){
		Line line1 = new Line(line.getStartPoint(), this.getStartPoint());
		Line line2 = new Line(line.getStartPoint(), this.getEndPoint());
		Line line3 = new Line(line.getEndPoint(),this.getEndPoint());
		Line line4 = new Line(line.getEndPoint(),this.getStartPoint());
		double angle1 = line1.angleBetweenLines(line2);
		double angle2 = line2.angleBetweenLines(line3);
		double angle3 = line3.angleBetweenLines(line4);
		double angle4 = line4.angleBetweenLines(line1);
		//not sure how precise Pi needs to be
		if(angle1+angle2+angle3+angle4<2*/*Math.PI*/3.1415926){
			return false;
		}else{
			return true;
		}
	}
	
	public double angleBetweenLines(Line line){
		//returns the smaller of the angles between two lines
		double angle=Math.abs(line.getAngle()-this.getAngle());
		if(angle>Math.PI){
			angle = 2*Math.PI-angle;
		}
		return angle;
	}
	
	//returns the actual smaller angle between two lines
//	public double realAngleBetweenLines(Line line){
//		double angle1 = this.m_angle;
//		double angle2 = line.getAngle();
//		if(angle2 < angle1 ){
//			System.out.println(angle1*360/Math.PI/2.);
//			System.out.println(angle2*360/Math.PI/2.);
//			System.out.println();
//			return angle1 - Math.PI - angle2;
//		}
//		return Math.PI - Math.abs(angle2-angle1);
//	}
	
	/**
	 * Intersects this line with another line. Returns the point of intersection. If there is no intersection returns null.
	 * @param line line to be intersected
	 * @return the point of intersection if there is one and otherwise <code>null</code>
	 */
	public Point intersect(Line line){
		if (!(Math.round(this.getAngle()*1000) == Math.round(line.getAngle()*1000) && !(Math.round(this.getAngle()*1000)-Math.round(Math.PI*1000) == Math.round(line.getAngle()*1000)) && !(Math.round(this.getAngle()*1000) == Math.round(line.getAngle()*1000)-Math.round(Math.PI*1000)))){
			double X1 = this.getEndPoint().getXPos() - this.getStartPoint().getXPos();
			double Y1 = this.getEndPoint().getYPos() - this.getStartPoint().getYPos();
			double X2 = line.getEndPoint().getXPos() - line.getStartPoint().getXPos();
			double Y2 = line.getEndPoint().getYPos() - line.getStartPoint().getYPos();
			double Q = line.getStartPoint().getXPos() - this.getStartPoint().getXPos();
			double P = line.getStartPoint().getYPos() - this.getStartPoint().getYPos();
			if ((Y1*X2 - Y2*X1)==0){
				return null;
			}
			double s = (P*X1 - Y1*Q)/(Y1*X2 - Y2*X1);
			return new Point(s*X2 + line.getStartPoint().getXPos(), s*Y2 + line.getStartPoint().getYPos());
		}
		else{
			return null;
		}
	}
	
	private void calcLength(Point startPoint, Point endPoint){
		Point point = new Point(endPoint.getXPos()-startPoint.getXPos(),endPoint.getYPos()-startPoint.getYPos());
		double x = point.getXPos()*point.getXPos();
		double y = point.getYPos()*point.getYPos();
		this.m_length = Math.sqrt(x+y);
	}
	
	private void calcAngle(Point startPoint, Point endPoint, double length){
		double y = endPoint.getYPos()-startPoint.getYPos();
		double x = endPoint.getXPos()-startPoint.getXPos();
		this.m_angle = Math.asin(y/length);
		if(m_angle<0){
			m_angle = m_angle+2*Math.PI;
		}
		if(endPoint.getXPos()<startPoint.getXPos()){
			m_angle = Math.abs(Math.PI-m_angle);
		}
		if(y<0 && x<0){
			m_angle = 2*Math.PI-m_angle;
		}
	}
	
	private void calcEndPoint(double length, double angle, Point startPoint){
		double x = Math.cos(angle)*length + startPoint.getXPos();
		double y = Math.sin(angle)*length + startPoint.getYPos();
		this.m_endPoint.setXPos(x);
		this.m_endPoint.setYPos(y);
	}
	
	private void calcStartPoint(double length, double angle, Point endPoint){
		if(angle>Math.PI){
			angle = angle-Math.PI;
		}else{
			angle = angle+Math.PI;
		}
		double x = Math.cos(angle)*length + endPoint.getXPos();
		double y = Math.sin(angle)*length + endPoint.getYPos();
		this.m_startPoint.setXPos(x);
		this.m_startPoint.setYPos(y);
	}	
	/**
	 * Returns the angle of this line.
	 * @return the angle of this line
	 */
	public double getAngle() {
		return m_angle;
	}
	/**
	 * Returns the length of this line.
	 * @return the length of this line
	 */
	public double getLength() {
		return m_length;
	}
	/**
	 * Returns the starting point of this line.
	 * @return the starting point of this line
	 */
	public Point getStartPoint() {
		return m_startPoint;
	}
	/**
	 * Returns the end point of this line.
	 * @return the end point of this line
	 */
	public Point getEndPoint() {
		return m_endPoint;
	}
	/**
	 * Resets the angle and keeps the same starting point and length. The end point is then recalculated.
	 * @param angle new angle
	 */
	public void setAngle(double angle) {
		this.m_angle = angle;
		calcEndPoint(m_length, m_angle, m_startPoint);
	}
	/**
	 * Resets the length and keeps the same starting point and length. The end point is then recalculated.
	 * @param length new length
	 */
	public void setLength(double length) {
		this.m_length = length;
		calcEndPoint(m_length, m_angle, m_startPoint);
	}
	/**
	 * Resets the starting point and keeps the length and angle. The end point is then recalculated.
	 * @param startPoint new starting point
	 */
	public void setStartPoint(Point startPoint) {
		this.m_startPoint.setXPos(startPoint.getXPos());
		this.m_startPoint.setYPos(startPoint.getYPos());
		calcEndPoint(m_length, m_angle, m_startPoint);
	}
	/**
	 * Resets the end point and keeps the length and angle. The starting point is then recalculated.
	 * @param endPoint new end point
	 */
	public void setEndPoint(Point endPoint) {
		this.m_endPoint.setXPos(endPoint.getXPos());
		this.m_endPoint.setYPos(endPoint.getYPos());
		calcStartPoint(m_length, m_angle, m_endPoint);
	}
	@Override
	public String toString(){
		String s = "";
		s = s+this.m_startPoint + tab + this.m_endPoint;
		return s;
	}
}
